package network;

import gui.Forme;
import gui.TableauBlancUI;

import java.net.MalformedURLException;
import java.rmi.Naming;
import java.rmi.NotBoundException;
import java.rmi.Remote;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;

import javax.swing.SwingUtilities;

import output.OutTrace;

public class Processus implements IProcessus, Runnable {
	
	private final int ELEC_BULLY = 0;
	private final int ELEC_CANDR = 1;
	private final int ELEC_DKANDR = 2;
	private int electionType;
	
	private final int TEMPO = 5000;
	private final int maxProcess = 10;
	private final String addressReso;
	
	private IReso reso;
	private TableauBlancUI tableau;
	
	private int id;
	private Integer master;
	private boolean isConnected;
	
	private Semaphore semSearchMaster;
	private Semaphore semWaitOneMaster;
	private Semaphore semDrawingRequest;
	private Semaphore semGetListOfForm;
	
	private boolean scAvailable;
	private int ackForme;
	private int nbForm;

	private List<Integer> listOfProcess;
	private Map<Integer,Forme> listOfForme;
	
	// For master
	private List<Integer> listOfProcessWait;
	private int idSc;
	
	// Bully
	private boolean election, ackReceive, newMaster;
	
	public OutTrace outTrace;

	
	// Chang and Roberts
	//private boolean concurrent;
	
	public Processus(String addressReso, int electionType) {
		
		this.addressReso = addressReso;
		this.electionType = electionType;
		
		this.id = -1;
		this.master = -1;
		this.scAvailable = false;
		this.isConnected = false;
		this.idSc = -1;
		this.ackForme = 0;
		this.nbForm = 0;
		
		this.listOfProcess = new ArrayList<Integer>(maxProcess);
		this.listOfForme = new HashMap<Integer,Forme>();
		
		this.listOfProcessWait = new ArrayList<Integer>();
		
		this.election = false;
		this.ackReceive = false;
		this.newMaster = false;
		
		this.semSearchMaster = new Semaphore(0);
		this.semWaitOneMaster = new Semaphore(0);
		this.semDrawingRequest = new Semaphore(0);
		this.semGetListOfForm = new Semaphore(0);
		
		this.etatCourant = SLEEP;
		
	}
	
	public void start() {
		
		connectReso();
		joinGroup();
		if (this.id >= 0) {
			startUI();
		}
	}

	@Override
	public void run() {
		try {
			startElection();
		} catch (RemoteException e) {
			e.printStackTrace();
		}
	}
	
/** PARTIE CHANG & ROBERTS ********************************************************/	
	
	private static final int INIT = 1;
	private static final int LEADER = 2;
	private static final int LOST = 3;
	private static final int SLEEP = 4;
	private static final int PARTICIPANT = 5;
	
	private int etatCourant = SLEEP;
	
	public String toReadable(int a){
		String s = "";
		switch (a) {
		
		case INIT :
			s = "Init";
			break;
		case LEADER :
			s = "Leader";
			break;
		case LOST :
			s = "Lost";
			break;
		case SLEEP :
			s = "Sleep";
			break;
		case PARTICIPANT :
			s = "Participant";
			break;
		default :
			break;
		}
		return s;
	}
	
	public String anneau () {
			return "Process " + this.id + " : My neighbor is : " + getVoisin();
	}
	
	/* Retourne l'id du voisin */
	public int getVoisin() {
		int v=-1, ecartMin=100000;
		int min = 1000000;
		for(int i : this.listOfProcess){
			if(i<min){
				min=i;
			}
			if(i>this.id){
				if(i-this.id < ecartMin){
					ecartMin = i-this.id;
					v = i;
				}
			}
		}
		//return this.listOfProcess.get((this.listOfProcess.indexOf(this.id)+1)%this.listOfProcess.size());
		return (v==-1)?min:v;
	}
	
	public void startElection() throws RemoteException {
		
		switch (this.electionType) {
		
		case ELEC_BULLY :
			startElectionBully("remote");
			break;
		case ELEC_CANDR :
			startElectionCR();
			break;
		case ELEC_DKANDR :
			break;
		default :
			break;
		}
	}
	
	public void startElectionCR() throws RemoteException{
		//this.etatCourant = INIT;
		//System.out.println(this.listOfProcess);
		try {
			this.reso.sendTok(this.id, this.id,this.getVoisin());
		} catch (RemoteException e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * Fonction respectant l'algorithme donné en cours, seuls les ps initiateurs peuvent gagner l'éléction.
	 * 
	 */
	public void getTok(int idSource) throws RemoteException{
		
		System.out.println("Process " + this.id + " : <tok," + idSource + ">");
		/* Je suis un initiateur de l'election */
		if (this.etatCourant == INIT) {
			if(idSource == this.id){
				this.etatCourant = LEADER;
				this.master = this.id;
				System.out.println("Process " + this.id + " : I'm the new master");
				this.reso.sendLeader(this.id, this.getVoisin());
				
			} else{
				/* Il existe un processus d'id plus grand que moi, j'ai donc perdu l'election*/
				if (idSource > this.id) {
					this.etatCourant = LOST;
				}
				this.reso.sendTok(this.id, idSource, this.getVoisin());
			}
		/* Je ne suis pas un initiateur de l'election, je me contente de faire suivre le message a mon voisin */
		} else {
			
			this.reso.sendTok(this.id, idSource, this.getVoisin());
			if (this.etatCourant == SLEEP) {
				this.etatCourant = LOST;
			}
		}
	}
	
	/**
	 * Fonction modifiée, tous les processus peuvent gagner l'éléction
	 * 
	 */
	public void getTokv2(int idSource) throws RemoteException{
		
		System.out.println("Process " + this.id + " : <tok," + idSource + "> - current state : " + toReadable(this.etatCourant));
		

		/* Je reçois mon propre id => je deviens le nouveau maitre */
		if(idSource == this.id){
			this.etatCourant = LEADER;
			this.master = this.id;
			System.out.println("Process " + this.id + " : I'm the new master");
			this.reso.sendLeader(this.id, this.getVoisin());
			
		} else{
			
			/* Il existe un processus d'id plus grand que moi, je le transmet*/
			if (idSource > this.id) {
				this.etatCourant = LOST;
				this.reso.sendTok(this.id, idSource, this.getVoisin());
			}
			/* Mon id est plus grand que l'id recu, si je ne l'ai pas déja fait, je transmet mon propre id*/
			else{
				if(this.etatCourant == SLEEP){
					this.etatCourant = PARTICIPANT;
					this.reso.sendTok(this.id, this.id, this.getVoisin());
				}
				
			}
		}
		
	}
	
	public void receiveLeader(int idMaster) throws RemoteException{
		this.etatCourant = SLEEP;
		if(idMaster != this.id){
			this.master = idMaster;
			this.reso.sendLeader(idMaster, this.getVoisin());
			if(tableau != null) {
				tableau.majShowProcess(toStringListOfProcess());
			}
			this.semWaitOneMaster.release();
		}else{
			if(tableau != null) {
				tableau.majShowProcess(toStringListOfProcess());
			}
			this.semWaitOneMaster.release();
			//this.reso.sendLeader(this.id,this.getVoisin());
		}
	}
	
	@Override
	public void timeoutCR(int idSource) throws RemoteException {
		
		System.out.println("Process " + this.id + " : TIMEOUTCR from " + idSource);
		removeProcessus(idSource);
		/* Puisqu'on n'a pas reussi à contacter notre voisin, l'etat courant ne devrait pas avoir été modifié */
		this.etatCourant = SLEEP;
		this.getTokv2(this.getVoisin());
	}
	
/** FIN PARTIE CHANG & ROBERTS ****************************************************/
	
	public void startElectionBully(String s) {
		
		System.out.println("Process " + this.id + " : Starts election from " + s);
		outTrace.ecrire("Process " + this.id + " : Starts election from " + s);
		
		this.election = true;
		this.ackReceive = false;
		this.newMaster = false;
		
		for (int i = 0; i < this.listOfProcess.size(); i++) {
			if (this.listOfProcess.get(i) > this.id) {
				try {
					System.out.println("Process " + this.id + " : Sending election to " + this.listOfProcess.get(i));
					outTrace.ecrire("Process " + this.id + " : Sending election to " + this.listOfProcess.get(i));
					this.reso.sendMsgElection(this.id, this.listOfProcess.get(i));
				} catch (RemoteException e) {
					System.out.println("Process " + this.id + " : Error sending election to " + this.listOfProcess.get(i));
					outTrace.ecrire("Process " + this.id + " : Error sending election to " + this.listOfProcess.get(i));
				}
			}
		}
		
		synchronized (this) {
			if (!ackReceive) {
				try {
					this.wait(TEMPO);
				} catch (InterruptedException e) {
					System.out.println("Process " + this.id + " : ackRecu : " + this.id + " is not the master");
					outTrace.ecrire("Process " + this.id + " : ackRecu : " + this.id + " is not the master");
				}
			}
			if (ackReceive) {
				if (!newMaster) {
					try {
						this.wait();
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
			} 
		} 
			
		// Just to avoid calling neighbors in synchronized state
		if (!ackReceive) {
			System.out.println("*** Process " + this.id + " is the master ***");
			outTrace.ecrire("*** Process " + this.id + " is the master ***");
			this.master = this.id;
			// O do not need to send coordinator message to process higher than me
			for (int i = 0; i < this.listOfProcess.size(); i++) {
				if (this.listOfProcess.get(i) < this.id) {
					try {
						System.out.println("Process " + this.id + " : Sending master message to " + this.listOfProcess.get(i));
						outTrace.ecrire("Process " + this.id + " : Sending master message to " + this.listOfProcess.get(i));
						this.reso.sendMsgMaster(this.id, this.listOfProcess.get(i), this.master);
					} catch (RemoteException e1) {
						System.out.println("Process " + this.id + " : Error sending master message to " + this.listOfProcess.get(i));
						outTrace.ecrire("Process " + this.id + " : Error sending master message to " + this.listOfProcess.get(i));
					}
				}
			}
			if(tableau != null) {
				tableau.majShowProcess(toStringListOfProcess());
			}
			this.semWaitOneMaster.release();
		}
		this.election = false;
	}
	
	public synchronized void msgAck() throws RemoteException {
		System.out.println("Process " + this.id + " : Received ack msg");
		outTrace.ecrire("Process " + this.id + " : Received ack msg");
		this.ackReceive = true;
		this.notify();	
	}

	public synchronized void msgMaster(int proc) throws RemoteException {
		System.out.println("Process " + this.id + " : Received master from " + proc);
		outTrace.ecrire("Process " + this.id + " : Received master from " + proc);
		this.master = proc;
		this.newMaster = true;
		if(tableau != null) {
			tableau.majShowProcess(toStringListOfProcess());
		}
		this.notify();
		this.semWaitOneMaster.release();
	}

	public void msgElection(int proc) throws RemoteException {
		
		System.out.println("Process " + this.id + " : Received election from " + proc);
		outTrace.ecrire("Process " + this.id + " : Received election from " + proc);
		try {
			this.reso.sendMsgAck(this.id, proc);
		} catch (RemoteException re) {
			System.out.println("Process " + this.id + " : Error sending ack to " + proc);
			outTrace.ecrire("Process " + this.id + " : Error sending ack to " + proc);
		}
		if (!election) {
			Thread th = new Thread(this);
			th.start();
		}
	}
	
	public int getId() throws RemoteException {
		return id;
	}
	
	public int getLocalId() {
		return id;
	}

	public int getMasterId() {
		return master;
	}

	public void setId(int id) {
		this.id = id;
	}

	public void setMasterId(int master) {
		this.master = master;
	}
	
	public void connectReso() {
		try {
			this.reso = (IReso) Naming.lookup("//"+ this.addressReso +"/Reso");
		} catch(RemoteException e) {
			e.printStackTrace();
		} catch(NotBoundException e) {
			e.printStackTrace();
		} catch (MalformedURLException e) {
			e.printStackTrace();
		}
	}
	
	public void startUI() {
		
		SwingUtilities.invokeLater(new Runnable() {
			public void run() {
				tableau = new TableauBlancUI(Processus.this);
				Processus.this.displayAllForme();
				tableau.majShowProcess(toStringListOfProcess());
			}
		});
	}
	
	public void joinGroup() {
		
		int id = -1;
		
		try {
			// Request to join group -> return id
			id = reso.requestJoinGroup();
			if (id < 0) {
				System.out.println("Process : Error to join the group");
			} else {
				// Registry object on server reso
				this.id = id;
				Registry registry = LocateRegistry.getRegistry(1099);
                Remote stub = UnicastRemoteObject.exportObject(this, 0);
				registry.rebind("pid"+getId(), stub);
				reso.registerProcessus(id);
				this.outTrace = new OutTrace("out_processus_" + this.id);
				
				// Retrieve list of process
				this.listOfProcess = reso.getListOfProcess();
				// Check master
				// If master unavailable -> Start election
				searchMaster();
				show();

				// Retrieve list of form from master
				// TODO Attention, si c'est lui le maitre.....
				sendRequestListOfForme();
				try {
					this.semGetListOfForm.acquire();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				System.out.println("Process " + this.id + " join the group");
				outTrace.ecrire("Process " + this.id + " join the group");
				this.isConnected = true;
			}
		} catch (RemoteException e) {
			e.printStackTrace();
		}
	}

	
	public void leaveGroup() throws RemoteException {
		
		System.out.println("Process " + this.id + " leave the group");
		outTrace.ecrire("Process " + this.id + " leave the group");
		reso.requestLeaveGroup(this.id);
		this.id = -1;
		this.master = -1;
		this.idSc = -1;
		this.isConnected = false;
		this.listOfProcess = new ArrayList<Integer>(maxProcess);
		this.listOfForme = new HashMap<Integer,Forme>();
		
		this.election = false;
		this.ackReceive = false;
		this.newMaster = false;
		try {
			this.finalize();
		} catch (Throwable e) {
			e.printStackTrace();
		}
	}
	
	public void searchMaster() {

		System.out.println("Process " + Processus.this.id + " search the master");
		outTrace.ecrire("Process " + Processus.this.id + " search the master");
		
		new Thread(new Runnable() {
			public void run() {
				// copie de la liste
				List<Integer> cpListOfProcess = new ArrayList<Integer>(Processus.this.listOfProcess);
				
				for (int i = 0; i < cpListOfProcess.size(); i++) {
					if (Processus.this.id != cpListOfProcess.get(i)) {
						try {
							System.out.println("Process " + Processus.this.id + " : send searchmaster to " + cpListOfProcess.get(i));
							outTrace.ecrire("Process " + Processus.this.id + " : send searchmaster to " + cpListOfProcess.get(i));
							Processus.this.reso.searchMaster(Processus.this.id, cpListOfProcess.get(i));
						} catch (RemoteException e) {
							e.printStackTrace();
						}
					}
				}
				System.out.println("\n");
				outTrace.ecrire("\n");
			}
		}).start();
		
		try {
			this.semSearchMaster.tryAcquire(5, TimeUnit.SECONDS);
		} catch (InterruptedException e) {
			System.out.println("Process " + this.id + " : Interrupt when searchMaster");
			outTrace.ecrire("Process " + this.id + " : Interrupt when searchMaster");
			e.printStackTrace();
		}
		
		if (this.master == -1 && this.listOfProcess.size() >= 4) {
			System.out.println("Process " + this.id + " : Master not found");
			outTrace.ecrire("Process " + this.id + " : Master not found");
			try {
				startElection();
				this.semWaitOneMaster.acquire();
			} catch (InterruptedException e) {
				e.printStackTrace();
			} catch (RemoteException e) {
				e.printStackTrace();
			}
		} else {
			if (this.master == -1 && this.listOfProcess.size() < 4) {
				try {
					System.out.println("Process " + this.id + " : Wait other process for start election");
					outTrace.ecrire("Process " + this.id + " : Wait other process for start election");
					this.semWaitOneMaster.acquire();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}
	}
	
	public void responseSearchMaster(int idSource) throws RemoteException {
		
		System.out.println("Process " + this.id + " : Master is : " + idSource);
		outTrace.ecrire("Process " + this.id + " : Master is : " + idSource);
		this.master = idSource;
		this.semSearchMaster.release();
	}
	
	public synchronized void addProcessus(int idSource) throws RemoteException {
		
		System.out.println("Process " + this.id + " : Add new process : " + idSource);
		outTrace.ecrire("Process " + this.id + " : Add new process : " + idSource);
		this.listOfProcess.add(idSource);
		if (this.id == this.master) {
			System.out.println("Process " + this.id + " : Master : send \"I'm master\" to " + idSource);
			outTrace.ecrire("Process " + this.id + " : Master : send \"I'm master\" to " + idSource);
			this.reso.responseMasterReso(this.id, idSource);
		}

		if(tableau != null) {
			tableau.majShowProcess(toStringListOfProcess());
		}
	}

	public void removeProcessus(int idClient) {
		
		int i = 0;
		boolean found = false;
		while (i < this.listOfProcess.size() && !found) {
			if (this.listOfProcess.get(i) == idClient) {
				this.listOfProcess.remove(i);
				found = true;
				if(tableau != null) {
					tableau.majShowProcess(toStringListOfProcess());
				}
			}
			i++;
		}
	}
	
	public void sendTrace(final Forme forme) {
		
		if (this.isConnected) {
			System.out.println("Process " + this.id + " : Transfert trace ... ");
			outTrace.ecrire("Process " + this.id + " : Transfert trace ... ");
			show();
			new Thread(new Runnable() {
				public void run() {
					boolean requestOk = false;
					try {
						requestOk = sendDrawingRequest();
						if (requestOk) {
							// SC ok
							Processus.this.nbForm++;
							addForme(Processus.this.nbForm-1,forme);
							Processus.this.tableau.canvas.delivreForme(forme);
							sendForme(Processus.this.nbForm-1,forme);
						}
					} catch (RemoteException e) {
						e.printStackTrace();
					}
				}
			}).start();
		}
	}
	
	public boolean sendDrawingRequest() throws RemoteException {

		System.out.println("Process " + Processus.this.id +" : Request of SC ... ");
		outTrace.ecrire("Process " + Processus.this.id +" : Request of SC ... ");
		// Si le client est le maitre, on envoie rien et il se demande lui meme la section critique
		if (this.master == this.id) {
			if (this.idSc < 0) {
				this.idSc = this.id;
				System.out.println("Process " + Processus.this.id +" : i'm master, SC available");
				outTrace.ecrire("Process " + Processus.this.id +" : i'm master, SC available");
				return true;
			} else {
				System.out.println("Process " + Processus.this.id +" : i'm master, SC unavailable");
				outTrace.ecrire("Process " + Processus.this.id +" : i'm master, SC unavailable");
				return false;
			}
		// Sinon on envoie une demande au maitre
		} else {
			this.scAvailable = false;
			System.out.println("Process " + this.id + " : Send request of SC to master : " + this.master);
			outTrace.ecrire("Process " + this.id + " : Send request of SC to master : " + this.master);
			reso.sendDrawingRequestReso(this.id, this.master);
			
			try {
				this.semDrawingRequest.tryAcquire(5, TimeUnit.SECONDS);
			} catch (InterruptedException e) {
				System.out.println("Process " + this.id + " : Interrupt");
				outTrace.ecrire("Process " + this.id + " : Interrupt");
				e.printStackTrace();
			}
			
			return this.scAvailable;
		}
	}

	public synchronized void receiveDrawingResponse(int idSource, boolean scAvailable) throws RemoteException {

		this.scAvailable = scAvailable;
		if (this.scAvailable) {
			System.out.println("Process " + this.id + " : Master replies SC avaiblable");
			outTrace.ecrire("Process " + this.id + " : Master replies SC avaiblable");
		} else {
			System.out.println("Process " + this.id + " : Master replies SC unavaiblable");
			outTrace.ecrire("Process " + this.id + " : Master replies SC unavaiblable");
		}
		this.semDrawingRequest.release();
	}

	public synchronized void receiveDrawingRequestMaster(int idSource) throws RemoteException {
		
		System.out.println("Process " + this.id + " : Master receive request of SC");
		outTrace.ecrire("Process " + this.id + " : Master receive request of SC");
		if (this.idSc < 0) {
			System.out.println("Process " + this.id + " : Master : Send SC OK to " + idSource);
			outTrace.ecrire("Process " + this.id + " : Master : Send SC OK to " + idSource);
			this.idSc = idSource;
			this.reso.receiveDrawingResponseReso(idSource, true);
		} else {
			System.out.println("Process " + this.id + " : Master : Send SC KO to : " + idSource + ", " + this.idSc + " have currently SC");
			outTrace.ecrire("Process " + this.id + " : Master : Send SC KO to : " + idSource + ", " + this.idSc + " have currently SC");
			this.reso.receiveDrawingResponseReso(idSource, false);
		}
	}
	
	public void sendEndOfSC() throws RemoteException{

		if (this.id == this.master) {
			sendListOfFormeToNewsProcess();
			this.idSc = -1;
			System.out.println("Process " + this.id + " : Master : SC now available");
			outTrace.ecrire("Process " + this.id + " : Master : SC now available");
		} else {
			System.out.println("Process " + this.id + " : Send end of SC to master : " + this.master);
			outTrace.ecrire("Process " + this.id + " : Send end of SC to master : " + this.master);
			this.reso.sendEndSC(this.id, this.master);
			this.scAvailable = false;
		}
	}

	@Override
	public void receiveEndOfSC(int idSource) throws RemoteException {
		
		if (this.master == this.id) {
			if (this.idSc == idSource) {
				sendListOfFormeToNewsProcess();
				this.idSc = -1;
				System.out.println("Process " + this.id + " : Master : SC now available");
				outTrace.ecrire("Process " + this.id + " : Master : SC now available");
			}
		}
	}
	
	public void sendListOfFormeToNewsProcess() throws RemoteException {
		
		// On envoie a tous les nouveaux la liste des formes a jour
		for (int i = 0; i < this.listOfProcessWait.size(); i++) {
			System.out.println("Process " + this.id + " : Master : Send list of form to " + this.listOfProcessWait.get(i));
			System.out.println("Process " + this.id + " : Master : List to send : " + this.listOfForme.toString());
			outTrace.ecrire("Process " + this.id + " : Master : Send list of form to " + this.listOfProcessWait.get(i));
			outTrace.ecrire("Process " + this.id + " : Master : List to send : " + this.listOfForme.toString());
			this.reso.sendListOfFormeReso(this.id, this.listOfProcessWait.get(i), this.listOfForme);
		}
		// Remise a zero de la liste des proc en attente de la liste de forme
		this.listOfProcessWait.clear();
	}
	
	public void sendForme(int idForme, Forme forme) throws RemoteException {

		List<Integer> cpListOfProcess = new ArrayList<Integer>(this.listOfProcess);
		this.ackForme = cpListOfProcess.size()-1;
		
		if (cpListOfProcess.size() == 1) {
			sendEndOfSC();
		} else {
			for (int i = 0; i < cpListOfProcess.size(); i++) {
				if (this.id != cpListOfProcess.get(i)) {
					System.out.println("Process " + this.id + " : Send form to " + cpListOfProcess.get(i));
					outTrace.ecrire("Process " + this.id + " : Send form to " + cpListOfProcess.get(i));
					this.reso.sendFormeReso(this.id, cpListOfProcess.get(i), idForme, forme);
				}
			}
		}
	}

	public synchronized void receiveForme(int idSource, int idForme, Forme forme) throws RemoteException {
		
		System.out.println("Process " + this.id + " : Receive form from " + idSource);
		outTrace.ecrire("Process " + this.id + " : Receive form from " + idSource);
		if (this.isConnected) {
			addForme(idForme,forme);
			this.nbForm++;
			this.tableau.canvas.delivreForme(forme);
		}
		sendAckForme(idSource);
	}
	
	public void sendAckForme(int idDest) throws RemoteException {
		
		reso.sendAckForme(this.id, idDest);
	}

	@Override
	public void receiveAckForme(int idSource) throws RemoteException {
		
		this.ackForme--;
		System.out.println("Process " + this.id + " : Receive AckForme from " + idSource);
		outTrace.ecrire("Process " + this.id + " : Receive AckForme from " + idSource);
		if (this.ackForme == 0) {
			System.out.println("Process " + this.id + " : Receive all AckForme");
			outTrace.ecrire("Process " + this.id + " : Receive all AckForme");
			sendEndOfSC();
		}
	}
	
	public void sendRequestListOfForme() throws RemoteException {
		
		reso.sendRequestListOfFormeReso(this.id, this.master);
	}

	@Override
	public void receiveRequestListOfForme(int idSource) throws RemoteException {
		
		// si c'est bien le maitre
		if (this.id == this.master) {
			System.out.println("Process " + this.id + " : Master : Receive request of listOfForm from " + idSource);
			outTrace.ecrire("Process " + this.id + " : Master : Receive request of listOfForm from " + idSource);
			if (this.idSc >= 0) {
				// attendre que la sc se lib�re
				// on ajoute le processus dans une liste d'attente pour lui renvoyer ensuite la liste des forme
				System.out.println("Process " + this.id + " : Master : SC unavailable, save process to send list of form later");
				outTrace.ecrire("Process " + this.id + " : Master : SC unavailable, save process to send list of form later");
				this.listOfProcessWait.add(idSource);
			} else {
				System.out.println("Process " + this.id + " : Master : SC available, send list of form");
				System.out.println("Process " + this.id + " : Master : ListOfForme = " + this.listOfForme.toString());
				outTrace.ecrire("Process " + this.id + " : Master : SC available, send list of form");
				outTrace.ecrire("Process " + this.id + " : Master : ListOfForme = " + this.listOfForme.toString());
				reso.sendListOfFormeReso(this.id, idSource, this.listOfForme);
			}
		}
	}
	
	@Override
	public void receiveListOfForme(int idSource, Map<Integer,Forme> listOfForme) throws RemoteException {
		
		System.out.println("Process " + this.id + " : Receive list of form : " + listOfForme);
		outTrace.ecrire("Process " + this.id + " : Receive list of form : " + listOfForme);
		this.listOfForme.putAll(listOfForme);
		if (listOfForme.size() > 0) {
			this.nbForm = Collections.max(listOfForme.keySet())+1;
		} else {
			this.nbForm = 0;
		}
		this.semGetListOfForm.release();
	}

	public void addForme(int idForme, Forme forme) {
		
		System.out.println("Process " + this.id + " : Add a forme " + idForme + " -> " + forme);
		outTrace.ecrire("Process " + this.id + " : Add a forme " + idForme + " -> " + forme);
		this.listOfForme.put(idForme,forme);
	}
	
	public void displayAllForme() {
		
		if (!this.listOfForme.isEmpty()) {
			for(int i = 0; i < this.listOfForme.size(); i++) {
				this.tableau.canvas.delivreForme(this.listOfForme.get(i));
			}
		}
	}

	@Override
	public void timeout(int idSource) throws RemoteException {
		
		System.out.println("Process " + this.id + " : TIMEOUT from " + idSource);
		outTrace.ecrire("Process " + this.id + " : TIMEOUT from " + idSource);
		ackForme--;
		// Delete the process
		removeProcessus(idSource);
		if (idSource == this.master) {
        	System.out.println("Process " + this.id + " : TIMEOUT -> Master unavailable ... ");
        	outTrace.ecrire("Process " + this.id + " : TIMEOUT -> Master unavailable ... ");
			startElection();
        }
	}
	
	public void show() {
		
		System.out.println("Process " + this.id + " : ");
		System.out.println("Master is " + this.master);
		outTrace.ecrire("Process " + this.id + " : ");
		outTrace.ecrire("Master is " + this.master);
		showListOfProcess();
		System.out.println("\n");
	}

	public String toStringListOfProcess() {
		
		StringBuffer sb = new StringBuffer();
		// copie de la liste
		List<Integer> cpListOfProcess = new ArrayList<Integer>(Processus.this.listOfProcess);
		Collections.sort(cpListOfProcess, Collections.reverseOrder());
		
		for (int i = 0; i < cpListOfProcess.size()-1; i++) {
			if (this.master == cpListOfProcess.get(i).intValue()) {
				sb.append(" *" + cpListOfProcess.get(i) + "* -");
			} else {
				sb.append(" " + cpListOfProcess.get(i) + " -");
			}
		}
		if (this.master == cpListOfProcess.get(cpListOfProcess.size()-1).intValue()) {
			sb.append(" *" + cpListOfProcess.get(cpListOfProcess.size()-1) + " -");
		} else {
			sb.append(" " + cpListOfProcess.get(cpListOfProcess.size()-1) + " -");
		}
		
		return sb.toString();
	}
	
	public void showListOfProcess() {
		
		System.out.print("List of process : ");
		for (int i = 0; i < this.listOfProcess.size()-1; i++) {
			System.out.print(" " + this.listOfProcess.get(i) + " -");
		}
		System.out.print(" " + this.listOfProcess.get(this.listOfProcess.size()-1));
	}
	
	public static void main(String[] args) {
		
		if (args.length <= 0 || args.length > 2) {
			System.out.println("Impossible to start new processus, specify the server Reso's address and the type of election");
		} else {
			System.out.println("args[0] = " + args[0]);
			System.out.println("args[1] = " + args[1]);
			
			if (Integer.parseInt(args[1]) >= 0 && Integer.parseInt(args[1]) < 3) {
				
				Processus p = new Processus(args[0],Integer.parseInt(args[1]));
				p.start();
				
			} else {
				System.out.println("Specified election type invalid : 1/ Bully 2/ Chang and Roberts 3/ Dolev, Klawe and Rodeh");	
			}
		}
	}
}